/**
 * 
 */
package jgrit.gui.factory;

import java.awt.Color;
import java.awt.Component;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.AbstractAction;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import jgrit.options.BooleanOption;
import jgrit.options.HexOption;
import jgrit.options.MultiOption;
import jgrit.options.Option;
import jgrit.options.RangedIntOption;

import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

/**
 * Creates GUI Components from <code>Option</code>s.
 * 
 * @author Sam Hartsfield
 * @author Derek Olejnik
 *
 */
public class UIFactory {	
	/**
	 * Prevent instantiation from outside
	 */
	private UIFactory() {}
	
	
	public Component createFileSaveComponent(final Option option) {
		return createFileSelectComponent(option, FileDialog.SAVE);
	}
	
	/**
	 * Shows a simple frame containing the given component (e.g. can be used
	 * for testing a panel).
	 * 
	 * @param component The component to show
	 * @param title The title of the window
	 */
	public static void showTestFrame(Component component, String title) {
		JFrame frame = new JFrame(title);
		frame.add(component);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.pack();
		frame.setVisible(true);
	}


	/**
	 * Quickly create a component for an option and add it.
	 * 
	 * @param builder A DefaultFormBuilder that you're using to lay out components
	 * @param option The option to create a component for and attach
	 * @param columnSpan The number of columns to use for the option
	 */
	public static void addToBuilder(DefaultFormBuilder builder, Option option, int columnSpan) {
		if (option instanceof BooleanOption) {
			builder.append(createComponent(option), columnSpan);
		} else {
			builder.append(option.getName(), createComponent(option), columnSpan - 2);
		}
	}


	/**
	 * Quickly create a component for an option and add it.
	 * 
	 * @param builder A DefaultFormBuilder that you're using to lay out components
	 * @param option The option to create a component for and attach
	 */
	public static void addToBuilder(DefaultFormBuilder builder, Option option) {
		if (option instanceof BooleanOption) {
			builder.append(createComponent(option), 3);
		} else {
			builder.append(option.getName(), createComponent(option));
		}
	}


	/**
	 * Create a a panel containing a text field and a button, which will
	 * be for selecting a file. The Option will be connected to the text field. 
	 * 
	 * @param option Option to attach to the text field
	 * @param mode The mode, either FileDialog.SAVE or FileDialog.LOAD
	 * @return The new panel that was created
	 */
	public static JPanel createFileSelectComponent(final Option option, final int mode) {
		/**
		 * Bounded column for text field fixes a bug by keeping the field
		 * from growing too large when the file path is long
		 */
		FormLayout layout = new FormLayout(
				"[pref,20dlu]:grow, $rgap, p", 
				"p");
		JPanel panel = new JPanel(layout);
		
		CellConstraints cc = new CellConstraints();
		final JTextField textField = new JTextField();
		final JButton selectButton = new JButton("...");
		
		selectButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (System.getProperty("os.name").contains("Linux")) {
					JFileChooser dialog = new JFileChooser();
					int returnVal = dialog.showOpenDialog(null);
					if (returnVal == JFileChooser.APPROVE_OPTION) {
						textField.setText(dialog.getSelectedFile().getAbsolutePath());
					}
				} else {
					FileDialog dialog = new FileDialog((Frame) null, option
							.getName(), mode);
					dialog.setVisible(true);

					if (dialog.getFile() != null && dialog.getDirectory() != null) {
						textField.setText(dialog.getDirectory() + dialog.getFile());
					}
				}
			}
		});
		
		panel.add(textField, cc.xy(1, 1));
		panel.add(selectButton, cc.xy(3, 1));
		
		attachOption(option, textField);
		
		return panel;
	}
	
	/**
	 * Creates a component given an option
	 * @param option - instance of Option class
	 * @return Instance of the created component
	 */
	public static Component createComponent(final Option option) {
		if (option instanceof MultiOption) {
			return createComponent((MultiOption)option);
		} else if (option instanceof BooleanOption) {
			return createComponent((BooleanOption)option);
		}
		
		//Since we're here, we know it's a text field of some kind
		
		final JTextField component = new JTextField();
		attachOption(option, component);
		
		return component;
	}



	/**
	 * Hook up an option and a text field
	 * 
	 * @param option
	 * @param component
	 */
	public static void attachOption(final Option option,
			final JTextField component) {
		component.setText(option.getValue());

		component.getDocument().addDocumentListener(new DocumentListener() {
			public void changedUpdate(DocumentEvent e) {

			}

			public void insertUpdate(DocumentEvent e) {
				option.setValue(component.getText());
			}

			public void removeUpdate(DocumentEvent e) {
				option.setValue(component.getText());
			}
		});

		option.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (!component.getText().equals(option.getValue())) {
					component.setText(option.getValue());
				}
			}
		});
	}
	
	
	/**
	 * Hook up an ranged integer option and a text field; this sets up a key
	 * listener to reject non-numeric characters
	 * 
	 * @param option
	 * @return
	 */
	public static void attachOption(final RangedIntOption option,
			final JTextField component) {
		attachOptionChecked(option, component);
	}


	/**
	 * Attach an Option that checks the keys typed and the value entered.
	 * The text box will be colored red if it contains an invalid value,
	 * and an invalid character typed will be rejected.
	 * 
	 * @param option
	 * @param component
	 */
	public static void attachOptionChecked(final Option option,
			final JTextField component) {
		attachOption(option, component);
		
		final Color origBackGround = component.getBackground();
		
		component.addKeyListener(new KeyAdapter() {
			public void keyTyped(KeyEvent e) {
				char c = e.getKeyChar();
				if (!option.isCharValid(c) && !(c == KeyEvent.VK_BACK_SPACE)
						&& !(c == KeyEvent.VK_DELETE)) {
					/*
					 * In this case, the character was not valid, so consume it.
					 * This happens UNLESS the character is valid, or if it
					 * is a backspace or delete.
					 */
					Toolkit.getDefaultToolkit().beep();
					e.consume();
				}
			}
		});
		
		// Set the background to red if the entry is invalid
		option.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (option.validate()) {
					component.setBackground(origBackGround);
				} else {
					component.setBackground(Color.RED.brighter());
				}
			}
		});
	}
	
	/**
	 * Hook up a hex option and a text field; this sets up a key
	 * listener to reject incorrect characters
	 * 
	 * @param option
	 * @return
	 */
	public static void attachOption(final HexOption option,
			final JTextField component) {
		attachOptionChecked(option, component);
	}
	
	private static Component createComponent(final MultiOption option) {
		final JComboBox component = new JComboBox(option.getPossibleValues());
		attachOption(option, component);
		return component;
	}


	/**
	 * Hook up an option and a combo box
	 * 
	 * @param option
	 * @param component
	 */
	public static void attachOption(final MultiOption option,
			final JComboBox component) {
		component.setModel(new DefaultComboBoxModel(option.getPossibleValues()));
		component.setSelectedItem(option.getValue());

		component.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				option.setValue(component.getSelectedItem().toString());
			}
		});
		
		option.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				component.setSelectedItem(option.getValue());
			}
		});
	}
	
	private static Component createComponent(final BooleanOption option) {
//		final JCheckBox component = new JCheckBox(option.getName(), Boolean
//				.parseBoolean(option.getValue()));
		final JCheckBox component = new JCheckBox();
		attachOption(option, component);
		return component;
	}


	/**
	 * Hook up an option and a check box
	 * 
	 * @param option
	 * @param component
	 */
	public static void attachOption(final BooleanOption option,
			final JCheckBox component) {
		component.setText(option.getName());
		component.setSelected(Boolean.parseBoolean(option.getValue()));
		component.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				option.setValue(Boolean.toString(component.isSelected()));
			}
		});
		
		option.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				component.setSelected(Boolean.parseBoolean(option.getValue()));
			}
		});
	}
	
	public static void main(String[] args) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout(
				"pref:grow, 3dlu, pref:grow"));
		builder.setDefaultDialogBorder();
		
		final Option option = new MultiOption("bpp", "gfx", "4",
				new String[] { "1", "2", "4", "8", "16" });
		
		final Option option2 = new Option("filename", "File", "");
		
//		final Option option3 = new BooleanOption("Select Me", "Truthiness",
//				"false");
		
		
		builder.append(UIFactory.createComponent(option));
		builder.append(UIFactory.createComponent(option));
		builder.nextLine();
		
		builder.append(UIFactory.createComponent(option2));
		builder.append(UIFactory.createComponent(option2));
		builder.nextLine();
				
//		builder.append(GritGUIFactory.createComponent(option3));
//		builder.append(GritGUIFactory.createComponent(option3));
//		builder.nextLine();
		
		builder.append(new JButton(new AbstractAction("Print Option value") {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				System.out.println(option.getValue());
			}
		}), 3);
		
		frame.add(builder.getPanel());
		frame.pack();
		frame.setVisible(true);
	}
}
